package com.aabte.lota.auth;

import com.aabte.commons.basic.util.ExceptionUtils;
import com.aabte.commons.rest.api.ApiResult;
import com.aabte.commons.rest.enums.StandardErrorCode;
import com.aabte.commons.rest.exception.ApiException;
import com.aabte.lota.auth.common.bean.dto.ArgumentInvalidDTO;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.Filter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.authz.AuthorizationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

/**
 * @author Daniel
 * @version 1.0
 * @date 2020/4/1
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

  private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

  @Value("${spring.application.name}")
  private String applicationName;

  @Resource private ExceptionHandlerFilter exceptionHandlerFilter;

  @Bean
  public FilterRegistrationBean<Filter> exceptionHandlerFilterRegistrationBean() {
    FilterRegistrationBean<Filter> filter = new FilterRegistrationBean<>();
    filter.setFilter(exceptionHandlerFilter);
    filter.setOrder(0);
    return filter;
  }

  /** 鉴权相关异常处理 */
  @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
  @ExceptionHandler(value = AuthorizationException.class)
  public ApiResult<Object> handle(
      ServletRequest request, ServletResponse response, AuthorizationException e) {
    logger.warn(ExceptionUtils.getErrorStack(e));
    return ApiResult.failed(
            StandardErrorCode.Unauthorized.getCode(), StandardErrorCode.Unauthorized.getMsg())
        .setProvider(applicationName);
  }

  /** HTTP方法不支持异常 */
  @ResponseStatus(code = HttpStatus.BAD_REQUEST)
  @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
  public ApiResult<String> handle(
      ServletRequest request, ServletResponse response, HttpRequestMethodNotSupportedException e) {
    String data = "Request method supported : " + e.getSupportedHttpMethods();
    return ApiResult.failed(StandardErrorCode.BadRequest.getCode(), e.getMessage(), data)
        .setProvider(applicationName);
  }

  /** 消息格式非法异常处理 */
  @ResponseStatus(code = HttpStatus.BAD_REQUEST)
  @ExceptionHandler(value = HttpMessageConversionException.class)
  public ApiResult<Object> handle(
      ServletRequest request, ServletResponse response, HttpMessageConversionException e) {
    return ApiResult.failed(StandardErrorCode.BadRequest)
        .setProvider(applicationName)
        .setProvider(applicationName);
  }

  /** 参数校验异常处理 */
  @ResponseStatus(code = HttpStatus.BAD_REQUEST)
  @ExceptionHandler(value = MethodArgumentNotValidException.class)
  public ApiResult<List<ArgumentInvalidDTO>> handle(
      ServletRequest request, ServletResponse response, MethodArgumentNotValidException e) {

    // 按需重新封装需要返回的错误信息
    List<ArgumentInvalidDTO> invalidArguments = new ArrayList<>();
    // 解析原错误信息，封装后返回，此处返回非法的字段名称，原始值，错误信息
    for (FieldError error : e.getBindingResult().getFieldErrors()) {
      ArgumentInvalidDTO invalidArgument = new ArgumentInvalidDTO();
      invalidArgument.setMsg(error.getDefaultMessage());
      invalidArgument.setField(error.getField());
      invalidArgument.setValue(error.getRejectedValue());
      invalidArguments.add(invalidArgument);
    }

    return ApiResult.failed(
            StandardErrorCode.BadRequest.getCode(),
            StandardErrorCode.BadRequest.getMsg(),
            invalidArguments)
        .setProvider(applicationName);
  }

  /** 参数校验异常 */
  @ResponseStatus(code = HttpStatus.BAD_REQUEST)
  @ExceptionHandler(value = BindException.class)
  public ApiResult<List<Object>> handle(
      ServletRequest request, ServletResponse response, BindException e) {
    BindingResult bindingResult = e.getBindingResult();
    List<Object> jsonList = new LinkedList<>();
    if (bindingResult.hasErrors()) {
      bindingResult
          .getFieldErrors()
          .forEach(
              fieldError -> {
                Map<String, Object> jsonObject = new HashMap<>(2);
                jsonObject.put("name", fieldError.getField());
                jsonObject.put("msg", fieldError.getDefaultMessage());
                jsonList.add(jsonObject);
              });
    }
    return ApiResult.restResult(jsonList, StandardErrorCode.BadRequest)
        .setProvider(applicationName);
  }

  /** 业务逻辑异常 */
  @ExceptionHandler(value = ApiException.class)
  public ApiResult<Object> handle(
      ServletRequest request, ServletResponse response, ApiException e) {
    Long code = e.getCode();
    Integer status = e.getStatus();
    String msg = e.getMessage();
    if (null == code) {
      code = StandardErrorCode.InternalServerError.getCode();
    }
    if (null == status) {
      status = StandardErrorCode.InternalServerError.getStatus();
    }

    ((HttpServletResponse) response).setStatus(status);

    logger.warn("Rest request error, {}", ExceptionUtils.getErrorStack(e));
    return ApiResult.failed(code, msg).setProvider(applicationName);
  }

  /** * 404处理 */
  @ExceptionHandler(value = NoHandlerFoundException.class)
  @ResponseStatus(HttpStatus.NOT_FOUND)
  public ApiResult<Object> notFountHandler(HttpServletRequest request, NoHandlerFoundException e) {
    return ApiResult.failed(StandardErrorCode.NotFound);
  }

  /** 未知异常 */
  @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
  @ExceptionHandler(value = Throwable.class)
  public ApiResult<Object> handle(ServletRequest request, ServletResponse response, Throwable e) {
    logger.error("Error: handleBadRequest StackTrace : {}", ExceptionUtils.getErrorStack(e));
    return ApiResult.failed(StandardErrorCode.InternalServerError).setProvider(applicationName);
  }
}
